﻿<!DOCTYPE HTML>
<html lang="zh">
<head>
<title>在收到消息时 - 语法 &amp; 使用 | AutoHotkey v2</title>
<meta name="description" content="The 在收到消息时 function specifies a function or function 对象 to call automatically when the script receives the specified message." />
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<link href="../static/theme.css" rel="stylesheet" type="text/css" />
<script src="../static/content.js" type="text/javascript"></script>
<script type="text/javascript">$(function(){0<=window.navigator.userAgent.toLowerCase().indexOf("ucbrowser")&&CaoNiMaDeUc()})</script>
</head>
<body>

<h1>在收到消息时</h1>

<p>指定当脚本接收到指定消息时自动调用的<a href="../Functions.htm">函数</a>或<a href="../objects/Functor.htm">函数对象</a>.</p>

<pre class="Syntax"><span class="func">在收到消息时</span> MsgNumber <span class="optional">, Function, MaxThreads</span></pre>
<h2 id="Parameters">参数</h2>
<dl>

  <dt>MsgNumber</dt>
  <dd>
    <p>类型: <a href="../Concepts.htm#numbers">整数</a></p>
    <p>需要监听或查询的消息编号, 应该介于 0 和 4294967295(0xFFFFFFFF) 之间. 如果你不想监听<a href="../misc/SendMessageList.htm">系统消息</a>(即编号小于 0x400 的那些), 那么最好选择一个大于 4096(0x1000) 的数字. 这降低了可能对当前及将来版本的 AutoHotkey 内部所使用的消息的干扰.</p>
  </dd>

  <dt>Function</dt>
  <dd>
    <p>类型: <a href="../Concepts.htm#strings">字符串</a>或<a href="../Concepts.htm#objects">对象</a></p>
    <p><a href="../Functions.htm">函数</a>或<a href="../objects/Functor.htm">函数对象</a>的名称. 要传递原义的函数名称, 必须用引号("") 括起来.</p>
  </dd>

  <dt id="MaxThreads">MaxThreads</dt>
  <dd>
    <p>类型: <a href="../Concepts.htm#numbers">整数</a></p>
    <p>这个整数通常被省略, 在这种情况下, 监控函数一次只能处理一个<a href="../misc/Threads.htm">线程</a>. 这通常是最好的, 因为否则每当监控函数中断时, 脚本就会按时间顺序处理消息. 因此, 作为 <em>MaxThreads</em> 的替代方案, 可以考虑使用 <em>霸体</em>, 如<a href="#Critical">下</a>所示.</p>
    <p>指定 0 来取消注册之前由 <em>Function</em> 标识的函数.</p>
    <p>默认情况下, 当为一个 <em>MsgNumber</em> 注册了多个函数时, 会按照注册的顺序调用它们. 要在之前注册的函数之前注册一个函数, 请为 <em>MaxThreads</em> 指定一个负值. 例如, <code>在收到消息时 Msg, Fn, -2</code> 将 <code>Fn</code> 注册为在之前为 <em>Msg</em> 注册的任何其他函数之前被调用, 并且允许 <em>Fn</em> 最多有 2 个线程. 但是, 如果函数已经被注册了, 除非取消注册然后重新注册, 否则顺序不会改变.</p>
  </dd>

</dl>

<h2 id="Usage">使用</h2>

<p>任何数量的函数或<a href="../objects/Functor.htm">函数对象</a>都可以监视一个给定的 <em>MsgNumber</em>.</p>
<p>这两行中的任何一行都会注册一个函数对象, 以便在任何先前注册的函数 <strong>之后</strong> 被调用:</p>
<pre class="Syntax"><span class="func">在收到消息时</span> MsgNumber, Function     <em>; 选项 1 - 省略 MaxThreads</em>
<span class="func">在收到消息时</span> MsgNumber, Function, 1  <em>; 选项 2 - 指定 MaxThreads 为 1</em></pre>
<p>注册一个函数对象, 以便在任何先前注册的函数 <strong>之前</strong> 被调用:</p>
<pre class="Syntax"><span class="func">在收到消息时</span> MsgNumber, Function, -1</pre>
<p>要取消注册一个函数对象, 请将 <em>MaxThreads</em> 指定为 0:</p>
<pre class="Syntax"><span class="func">在收到消息时</span> MsgNumber, Function, 0</pre>

<h2 id="Failure">失败</h2>
<p>当 <em>Function</em> 出现以下情况时将抛出异常:</p>
<ol>
  <li>不是一个对象或用户定义的函数名; 或者</li>
  <li>已知需要四个以上的参数.</li>
</ol>

<h2 id="The_Functions_Parameters">函数的参数</h2>
<p><a href="../Functions.htm">函数</a>可以监视一个或多个消息并接受最多四个参数:</p>
<pre>MyMessageMonitor(wParam, lParam, msg, hwnd)
{
    ... body of function...
}</pre>
<p>尽管您赋予参数的名称并不重要, 但以下信息将按顺序分配给它们:</p>
<p>参数 #1: 消息的 WPARAM 值.<br>
参数 #2: 消息的 LPARAM 值.<br>
参数 #3: 消息号, 这在一个函数监视多个消息的情况下很有用.<br>
参数 #4: 发送消息的窗口或控件的 HWND(唯一 ID). HWND 可以直接用于 <a href="../misc/WinTitle.htm#ahk_id">WinTitle 参数</a>.</p>
<p>WPARAM 和 LPARAM 是无符号的 32 位整数(从 0 到 2<sup>32</sup>-1) 或有符号的 64 位整数(从 -2<sup>63</sup> 到 2<sup>63</sup>-1), 这取决于运行脚本的 exe 程序是 32 位还是 64 位. 对于 32 位的脚本, 如果传入参数预期为有符号整数, 则可以参照这个例子得到负数:</p>
<pre>如果 (内_指针大小 = 4 &amp;&amp; wParam &gt; 0x7FFFFFFF)  <em>; 检查 <a href="../Variables.htm#PtrSize">内_指针大小</a> 以确保脚本为 32 位.</em>
    wParam := -(~wParam) - 1</pre>
<p>如果不需要相应的信息, 你可以从列表末尾省略一个或多个参数, 但在这种情况下, 必须指定一个星号作为最后的参数. 例如, 一个定义为 <code>MyMsgMonitor(wParam, lParam, *)</code> 函数将只接收前两个参数, 而定义为 <code>MyMsgMonitor(*)</code> 的函数将不接收任何参数.</p>

<h2 id="Additional_Information_Available_to_the_Function">函数中可用的附加信息</h2>
<p>除了上面接收到的参数外, 该函数还可以查询内置变量 <a href="../Variables.htm#EventInfo">内_事件信息</a>, 如果消息是通过 发送消息 发送的, 则其为 0. 如果是通过 投递消息 发送的, 则其为消息发出时的 <a href="../Variables.htm#TickCount">tick-count 时间</a>.</p>
<p>监听函数的<a href="../misc/WinTitle.htm#LastFoundWindow">上次找到的窗口</a>初始与消息发送到的父窗口相同(即使消息是发送到控件的). 如果窗口是隐藏的, 但不是 界面 窗口(例如脚本的主窗口), 在使用它之前打开 <a href="DetectHiddenWindows.htm">检测隐藏窗口</a>. 例如:</p>
<pre>检测隐藏窗口 True
MsgParentWindow := 窗口存在()  <em>; 这里保存了消息发送的目标窗口的唯一 ID.</em></pre>

<h2 id="What_the_Function_Should_Return">函数应该 <em>返回</em>(返回) 什么</h2>
<p>如果监听函数使用不带任何参数的 <a href="Return.htm">返回</a>, 或指定空值如 &quot;&quot;(甚至没有使用 返回), 则当此函数结束时, 传入的消息将继续被正常处理. 同样的情况也会出现在函数使用 <a href="Exit.htm">Exits</a> 或者出现了运行时错误的时候(例如, <a href="Run.htm">运行</a>不存在的文件). 与此相反, 返回一个整数时会被作为回复立即发送; 即程序不会再进一步处理此消息. 例如, 监听 WM_LBUTTONDOWN(0x201) 的函数可以返回一个整数来阻止目标窗口接收到鼠标点击的通知. 在许多情况下(例如使用 <a href="PostMessage.htm">投递消息</a> 发送的消息), 返回哪个整数并不重要; 不过如果不确定, 0 通常是最安全的.</p>
<p>有效返回值的范围取决于运行脚本的 exe 文件是 32 位还是 64 位. 对于 32 位(<code><a href="../Variables.htm#PtrSize">内_指针大小</a> = 4</code>) 脚本, 非空的返回值必须在 -2<sup>31</sup> 和 2<sup>32</sup>-1 之间, 对于 64 位(<code><a href="../Variables.htm#PtrSize">内_指针大小</a> = 8</code>) 脚本, 必须在 -2<sup>63</sup> 和 2<sup>63</sup>-1 之间.</p>
<p>如果有多个函数监听一个给定的消息号, 它们会被逐一调用, 直到其中一个函数返回一个非空值.</p>

<h2 id="Remarks">备注</h2>
<p>与普通的函数调用不同, 当一个被监听的消息到达时, 函数会作为一个新的<a href="../misc/Threads.htm">线程</a>被调用. 因此, 函数会以设置的默认值启动, 例如 <a href="SendMode.htm">发送模式</a> 和 <a href="DetectHiddenWindows.htm">检测隐藏窗口</a>. 这个默认设置可以通过在<a href="../Scripts.htm#auto">脚本启动</a>中使用此函数来改变.</p>
<p>发送(发送)(而不是 Post) 到控件的消息不会被监听, 因为系统直接把它们发送给后台的控件了. 对于系统生成的消息来说, 这很少是一个问题, 因为大多数信息都是 Post 的.</p>
<p>任何具有活动消息监控器的脚本都会自动<a href="../Scripts.htm#persistent">持续运行</a>, 这意味着它不会退出, 直到使用 <a href="ExitApp.htm">退出应用</a>.</p>
<p id="Critical">如果由于之前有相同的消息到达, 而消息的函数仍在运行, 则不会再次调用函数(除非 <a href="#最大线程数">MaxThreads</a> 大于 1); 相反, 消息将被视为未被监控. 如果不希望出现这种情况, 可以通过在函数的第一行指定 <a href="Critical.htm">霸体</a> 作为函数的第一行来缓冲大于或等于 0x312 的消息, 直到其函数完成. 另外, <a href="Thread.htm">线程 Interrupt</a> 也可以达到同样的目的, 只要它持续的时间足够函数完成. 相反, 小于 0x312 的消息不能被 霸体 或 线程 Interrupt 缓冲 (然而, 霸体 可能会有帮助, 因为它检查消息的<a href="Critical.htm#Interval">频率较低</a>, 这给了函数更多的时间来完成). 保证不漏掉此类消息的唯一方法是确保函数在 6 毫秒内完成(尽管这个限制可以通过 <a href="Critical.htm#Interval"><em>霸体 30</em></a> 来提高). 一种方法是通过向自己的脚本<a href="PostMessage.htm">发布(Post)</a> 一个高于 0x312 的监控消息号, 让它为未来的线程排队. 该消息的函数应该使用 <a href="Critical.htm">霸体</a> 作为其第一行, 以确保其消息被缓冲.</p>
<p>如果在小于 0x312 的受监听消息当脚本完全不可中断时到达(例如在显示<a href="../objects/Menu.htm#Show">菜单</a>, 正在进行 <a href="SetKeyDelay.htm">KeyDelay</a>/<a href="SetMouseDelay.htm">MouseDelay</a>, 或剪贴板正被<a href="_ClipboardTimeout.htm">打开</a>) -- 则不会调用此函数而消息会被视为未监听的.  相反, 一个等于或大于 0x312 的受监听消息在这些不可中断期间会被缓冲起来; 即当脚本变成可中断时会调用它的处理函数.</p>
<p>如果消息号小于 0x312 的受监听消息在脚本仅由于 <a href="Thread.htm">线程 Interrupt</a> 或 <a href="Critical.htm">霸体</a> 的设置而不可中断时到达, 则会中断当前线程而调用消息处理函数. 相反, 等于或大于 0x312  的受监听消息则会被缓冲到线程结束或变成可中断之后.</p>
<p>在收到消息时 的<a href="../misc/Threads.htm">优先级</a>总是为 0. 因此, 如果当前线程的优先级大于 0 时将不会监听或缓冲任何消息.</p>
<p>监听系统消息(小于 0x400 的那些) 时应多加小心. 例如, 如果监听函数不会快速结束, 那么对消息的响应时间可能比系统预期的要长, 这可能会导致一些副作用. 如果监听函数返回一个整数来抑制对消息的进一步处理, 但系统期望不同的处理或响应方式时, 可能会发生不想要的行为.</p>
<p>当脚本显示系统对话框时(例如 <a href="MsgBox.htm">信息框</a>), 则不会监听到任何投递(Post) 到控件的消息. 例如, 如果脚本正显示 信息框 而用户点击一个 界面 窗口上的按钮, 则 WM_LBUTTONDOWN 消息会被直接发送到按钮而不会调用监听函数.</p>
<p>尽管外部程序可以使用 PostThreadMessage() 或其他 API 调用直接投递(Post) 消息给脚本的线程, 但不建议这么做, 因为如果此时脚本正显示系统窗口(例如 <a href="MsgBox.htm">信息框</a>) . 则消息会丢失. 相反, 通常最好投递或发送(发送) 消息到脚本的主窗口或其中的某个 界面 窗口.</p>
<h2 id="Related">相关</h2>
<p><a href="CallbackCreate.htm">创建回调</a>, <a href="OnExit.htm">在退出时</a>, <a href="OnClipboardChange.htm">在剪贴板内容改变时</a>, <a href="PostMessage.htm">投递消息</a>, <a href="SendMessage.htm">发送消息</a>, <a href="../Functions.htm">函数</a>, <a href="../misc/SendMessageList.htm">窗口消息列表</a>, <a href="../misc/Threads.htm">线程</a>, <a href="Critical.htm">霸体</a>, <a href="DllCall.htm">动态库调用</a></p>
<h2 id="Examples">示例</h2>
<div class="ex" id="ExLButtonDown">
<p><a href="#ExLButtonDown">#1</a>: 下面是个可运行脚本, 它监听在 界面 窗口中的鼠标点击. 相关主题: <a href="../objects/GuiOnEvent.htm#ContextMenu">ContextMenu</a> 事件</p>
<pre>MyGui := 界面.新建(, "Example Window")
MyGui.Add("Text",, "点击 anywhere in this window.")
MyGui.Add("Edit", "w200")
MyGui.OnEvent("Close", (*) =&gt; 退出应用())
MyGui.显示
在收到消息时 0x201, "WM_LBUTTONDOWN"

WM_LBUTTONDOWN(wParam, lParam, msg, hwnd)
{
    X := lParam &amp; 0xFFFF
    Y := lParam &gt;&gt; 16
    Control := ""
    thisGui := 界面自句柄(hwnd)
    thisGuiControl := 界面控件自句柄(hwnd)
    如果 thisGuiControl
    {
        thisGui := thisGuiControl.Gui
        Control := "`n(in control " . thisGuiControl.ClassNN . ")"
    }
    工具提示 "You left-clicked in 界面 window '" thisGui.Title "' at client coordinates " X "x" Y "." Control
}</pre>
</div>

<div class="ex" id="ExShutdown">
<p><a href="#ExShutdown">#2</a>: 下面的脚本检测系统的关机/注销动作并允许您中止它. 在 Windows Vista 及更高的版本中, 系统会显示一个用户界面, 显示哪个程序正在阻止关机/注销, 并允许用户强制关机/注销. 在较旧的操作系统上, 脚本显示一个确认提示. 相关主题: <a href="OnExit.htm">在退出时</a></p>
<pre><em>; 下面的 动态库调用 是可选的: 它告诉操作系统首先要关闭此脚本(在其他所有程序之前).</em>
动态库调用("kernel32.dll\SetProcessShutdownParameters", "UInt", 0x4FF, "UInt", 0)
在收到消息时(0x11, "WM_QUERYENDSESSION")
返回

WM_QUERYENDSESSION(wParam, lParam, *)
{
    ENDSESSION_LOGOFF := 0x80000000
    如果 (lParam &amp; ENDSESSION_LOGOFF)  <em>; 用户正在注销.</em>
        EventType := "Logoff"
    否则  <em>; 系统正在关机或重启.</em>
        EventType := "Shutdown"
    try
    {
        <em>; 设置显示操作系统关闭 UI 的提示. 我们不会显示自己的确认提示
        ; 因为我们只有 5 秒钟的时间, 操作系统会显示关机 UI
        ; 同样, 没有可见窗口的程序在没有提供原因的情况下无法阻止关机.</em>
        BlockShutdown("Example script attempting to prevent " EventType ".")
        返回 false
    }
    捕获
    {
        <em>; ShutdownBlockReasonCreate 不可用,
        ; 所以这可能是 Windows XP, 2003 或 2000, 我们实际上可以防止关机.</em>
        Result := 信息框(EventType " in progress. Allow it?",, "YN")
        如果 (Result = "Yes")
            返回 true  <em>; 通知操作系统允许关机/注销操作继续.</em>
        否则
            返回 false  <em>; 通知操作系统中止关机/注销操作.</em>
    }
}

BlockShutdown(Reason)
{
    <em>; 如果您的脚本具有可见的 GUI, 请使用它代替 内_脚本句柄.</em>
    动态库调用("ShutdownBlockReasonCreate", "ptr", 内_脚本句柄, "wstr", Reason)
    在退出时("StopBlockingShutdown")
}

StopBlockingShutdown(*)
{
    在退出时(内_此函数, 0)
    动态库调用("ShutdownBlockReasonDestroy", "ptr", 内_脚本句柄)
}</pre>
</div>

<div class="ex" id="ExCustom">
<p><a href="#ExCustom">#3</a>: 让脚本接收其他脚本或程序的自定义消息和最多两个数字(要发送字符串而不是数字, 请参阅下一个示例).</p>
<pre>在收到消息时 0x5555, &quot;MsgMonitor&quot;
在收到消息时 0x5556, &quot;MsgMonitor&quot;

MsgMonitor(wParam, lParam, msg, *)
{
    <em>; 由于尽快返回常常很重要, 所以最好使用 工具提示 而不是</em>
    <em>; 类似 信息框 的进行显示, 以避免阻止函数结束:</em>
    工具提示 "Message " msg " arrived:`nWPARAM: " wParam "`nLPARAM: " lParam
}

<em>; 下面的代码可用于其他脚本内来激发运行上面脚本中的函数:</em>
设置标题匹配模式 2
检测隐藏窗口 True
如果 窗口存在(&quot;Name of Receiving Script.ahk ahk_class AutoHotkey&quot;)
    投递消息 0x5555, 11, 22  <em>; 因为上面的 窗口存在, 所以消息被发送到 &quot;<a href="../misc/WinTitle.htm#LastFoundWindow">上次找到的窗口</a>&quot;.</em>
检测隐藏窗口 False  <em>; 必须在 投递消息 之后才能关闭.</em></pre>
</div>

<div class="ex" id="ExSendString">
<p><a href="#ExSendString">#4</a>: 从一个脚本发送任意长度的字符串到另一个脚本. 这是个可运行的示例. 两个脚本必须使用相同的<a href="../Concepts.htm#native-encoding">原生编码</a>. 要使用它, 请保存并运行下面的两个脚本, 然后按下 <kbd>Win</kbd>+<kbd>Space</kbd> 来显示 输入框 来让您输入字符串.</p>
<p>保存下面的脚本为 <strong>Receiver.ahk</strong>, 然后运行它:</p>
<pre filename="Receiver.ahk">#单例模式
在收到消息时 0x4a, &quot;Receive_WM_COPYDATA&quot;  <em>; 0x4a is WM_COPYDATA</em>
返回

Receive_WM_COPYDATA(wParam, lParam, msg, hwnd)
{
    StringAddress := 获取数值(lParam, 2*内_指针大小, "Ptr")  <em>; 检索 CopyDataStruct 的 lpData 成员.</em>
    CopyOfData := 获取字符串(StringAddress)  <em>; 从结构中复制字符串.</em>
    <em>; 比起 信息框, 应该用 工具提示 显示, 这样我们可以及时返回:</em>
    工具提示 内_脚本名 "`nReceived the following 字符串:`n" CopyOfData
    返回 true  <em>;  返回 1(true) 是回复此消息的传统方式.</em>
}</pre>
<p>保存下面的脚本为 <strong>Sender.ahk</strong>, 接着运行它. 然后, 按下 <kbd>Win</kbd>+<kbd>Space</kbd> 热键.</p>
<pre filename="Sender.ahk">TargetScriptTitle := "Receiver.ahk ahk_class AutoHotkey"

#space::  <em>; Win+Space 热键. 按下此热键会显示 输入框 用于输入消息字符串.</em>
{
    global TargetScriptTitle
    ib := 输入框("Enter some text to 发送:", "发送 text via WM_COPYDATA")
    如果 ib.Result = "Cancel"  <em>; 用户按下了取消按钮.</em>
        返回
    result := Send_WM_COPYDATA(ib.Value, TargetScriptTitle)
    如果 result = ""
        信息框 "发送消息 failed or timed out. Does the following WinTitle exist?:`n" TargetScriptTitle
    否则 如果 (result = 0)
        信息框 "Message sent but the target window responded with 0, which may mean it ignored it."
}

Send_WM_COPYDATA(ByRef StringToSend, ByRef TargetScriptTitle)  <em>; 在这种情况中使用 ByRef 能节约一些内存.
; 此函数发送指定的字符串到指定的窗口然后返回收到的回复.
; 如果目标窗口处理了消息则回复为 1, 而消息被忽略了则为 0.</em>
{
    CopyDataStruct := 创建缓冲区(3*内_指针大小)  <em>; 分配结构的内存区域.</em>
    <em>; 首先设置结构的 cbData 成员为字符串的大小, 包括它的零终止符:</em>
    SizeInBytes := (字符串长度(StringToSend) + 1) * 2
    置数值( "Ptr", SizeInBytes  <em>; 操作系统要求这个需要完成.</em>
          , "Ptr", 字符串指针(StringToSend)  <em>; 设置 lpData 为到字符串自身的指针.</em>
          , CopyDataStruct, 内_指针大小)
    Prev_DetectHiddenWindows := 内_检测隐藏窗口
    Prev_TitleMatchMode := 内_标题匹配模式
    检测隐藏窗口 True
    设置标题匹配模式 2
    TimeOutTime := 4000  <em>; 可选的. 等待 receiver.ahk 响应的毫秒数. 默认是 5000
    ; 必须使用发送 发送消息 而不是投递 投递消息.</em>
    RetValue := 发送消息(0x4a, 0, CopyDataStruct,, TargetScriptTitle,,,, TimeOutTime) <em>; 0x4a 是 WM_COPYDATA.</em>
    检测隐藏窗口 Prev_DetectHiddenWindows  <em>; 恢复调用者原来的设置.</em>
    设置标题匹配模式 Prev_TitleMatchMode         <em>; 同样.</em>
    返回 RetValue  <em>; 返回 发送消息 的回复给我们的调用者.</em>
}</pre>
</div>

<p>有关如何使用 在收到消息时 来接收网络连接上数据到达时的通知的演示, 请参阅 <a href="../scripts/index.htm#WinLIRC">WinLIRC 客户端脚本</a>.</p>

</body>
</html>